#include "sync.h"
#include "list.h"
#include "global.h"
#include "debug.h"
#include "interrupt.h"

// 初始化信号量
void sema_init(struct semaphore* psema, uint8_t value) {
    psema -> value = value;       // 初始化信号量
    list_init(&psema -> waiters); // 初始化信号量的等待队列
}

// 初始化锁 plock
void lock_init(struct lock* plock) {
    plock -> holder = NULL;
    plock -> holder_repeat_nr = 0;
    sema_init(&plock -> semaphore, 1); // 信号量的初始值设置为 1
}

// 信号量的 down 操作，即 获得锁
void sema_down(struct semaphore* psema) {
    // 关中断来保证原子操作
    enum intr_status old_status = intr_disable();
    while(psema -> value == 0) { // 若为 0，则表示目前没有锁可以获取，锁现在在别的线程那里
        ASSERT(!elem_find(&psema -> waiters, &running_thread() -> general_tag));
        if(elem_find(&psema -> waiters, &running_thread() -> general_tag)) {
            PANIC("sema_down: thread blocked has been in waiters_list.\n");
        }
        // 若当前线程无法获取到锁，即信号量为 0，则把当前线程先加入等待队列，其次将其设置为阻塞状态
        list_append(&psema -> waiters, &running_thread() -> general_tag);
        thread_block(TASK_BLOCKED); // 阻塞当前线程，直到被唤醒
    }
    // 这里线程被唤醒的条件是：信号量 > 0, 即当前有锁可以获取
    // 线程被唤醒后，即拿到锁后，执行如下代码：
    psema -> value--; // 设置信号量状态为不可获取状态（个人感觉这样说会合适点）
                      // 直接这样应该也行：psema = 0
    ASSERT(psema -> value == 0);
    intr_set_status(old_status); // 恢复之前的中断状态
}

// 信号量的 up 操作，即 释放锁
void sema_up(struct semaphore* psema) {
    enum intr_status old_status = intr_disable(); // 关中断，保证原子操作
    // 只有当信号量为 0 时才可以释放锁，因为 0 表示已经有线程得到锁了
    // 所以此时才有可能使后来欲获取锁的线程阻塞，因此进入等待队列(waiters)
    ASSERT(psema -> value == 0); 
    if(!list_empty(&psema -> waiters)) {
        // 弹出等待队列的首元素
        struct task_struct* thread_blocked = elem2entry(struct task_struct, general_tag, list_pop(&psema -> waiters));
        // 解除该元素的阻塞
        thread_unblock(thread_blocked);
    }
    psema -> value++; // 将当前的锁释放（其实这里我感觉应该说成 设置信号量为可获取状态 会合适点）
                      // 直接这样应该也行：psema = 1
    ASSERT(psema -> value == 1);
    intr_set_status(old_status); // 恢复之前的中断状态
}

// 获取锁
void lock_acquire(struct lock* plock) {
    if(plock -> holder != running_thread()) { // 排除自己曾经获得了锁但未将其释放的情况
        sema_down(&plock -> semaphore); // 对信号量进行 P 操作，该操作内部已关闭中断，因此该操作具备原子性
        plock -> holder = running_thread(); // 将 holder 指向当前线程，即当前线程的锁持有者
        ASSERT(plock -> holder_repeat_nr == 0);
        plock -> holder_repeat_nr = 1; // 表示第一次获取到锁
    } else {
        plock -> holder_repeat_nr++; // 该线程在已经获得锁的情况下，多次重复获取锁
    }
}

// 释放锁
void lock_release(struct lock* plock) {
    ASSERT(plock -> holder == running_thread());
    if(plock -> holder_repeat_nr > 1) { // 这里并不是真正的释放锁，因为 holder_repeat_nr != 1，
                                        // 也就是说之前获取太多次锁了，需要依次逻辑释放(因为只是用计数量表示，并不是真的进行释放操作，所以我称为逻辑释放)
        plock -> holder_repeat_nr--;
        return;
    }
    // 以下是“真”释放
    ASSERT(plock -> holder_repeat_nr == 1);
    plock -> holder = NULL; // 必然把锁的持有者设置为空的这句放到 V 之前
    plock -> holder_repeat_nr = 0;
    sema_up(&plock -> semaphore); // V 操作
}
